﻿#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//strlen 的使用和模拟实现
    //size_t strlen ( const char * str );
    //字符串以 '\0' 作为结束标志，strlen函数返回的是在字符串中 '\0' 前面出现的字符个数（不包含 '\0' )。
    //参数指向的字符串必须要以 '\0' 结束。
    //注意函数的返回值为 size_t ，是无符号的（ 易错 ）
    //strlen的使用需要包含头文件

#include <string.h>
//int main()
//{
//    if (strlen("abc") - strlen("abcdef") > 0)
//    {
//        printf(">\n");
//    }
//    else
//    {
//        printf("<=\n");
//    }
//	return 0;
//} //这段代码的结果很多人第一次都会掉坑，认为输出是<=,原因就是认为strlen("abc")值为3，strlen("abcdef")值为6，3-6<0
//  //但是strlen函数的返回值为 size_t 是无符号整型，这个点是非常容易忽视的，但很重要，这样的话两个值相减返回的类型还是size_t
//  //所以得到的值-3的最高位就不是符号位而是数值位了，是一个非常大的大于0的数

//strlen的模拟实现：
//计数器方式
//int my_strlen(const char* str)
//{
//    int count = 0;
//    while (*str)
//    {
//        count++;
//        str++;
//    }
//    return count;
//}
//int main()
//{
//    char arr[] = "abcdef";
//    size_t len = my_strlen(arr);
//    printf("%zd\n", len);
//    return 0;
//}

//递归
//int my_strlen(const char* str)
//{
//    if (*str == '\0')
//    {
//        return 0;
//    }
//    else
//    {
//        return 1 + my_strlen(str+1);
//    }
//}
//int main()
//{
//    char arr[] = "abcdef";
//    size_t len = my_strlen(arr);
//    printf("%zd\n", len);
//    return 0;
//}

//指针-指针的方式
//int my_strlen(const char* str)
//{
//    char* p = str;
//    while (*str != '\0')
//    {
//        str++;
//    }
//    return str - p;
//}
//int main()
//{
//    char arr[] = "abcdef";
//    size_t len = my_strlen(arr);
//    printf("%zd\n", len);
//    return 0;
//}

//长度不受限制的字符串函数：strcpy、strcat、strcmp

//strcpy 的使用和模拟实现
    //str - string cpy - copy --> 字符串拷贝
    //char* strcpy(char * destination, const char * source );
    //                      目的地                   源头 把右边的内容拷贝到左边
//int main()
//{
//    char arr1[] = "Hello world";
//    char arr2[20] = "0";
//    strcpy(arr2, arr1);
//    printf("%s\n", arr2);
//    return 0;
//}
    //源字符串必须以 '\0' 结束。
    //会将源字符串中的 '\0' 拷贝到目标空间。
    //目标空间必须足够大，以确保能存放源字符串。
    //目标空间必须可修改。如果上述char arr2[20] = "0";改成char* p = "xxxxxxxxxxx",代码会报错，
    //                                                原因就是char* p = "xxxxxxxxxxx"为常量字符串，常量字符串不允许修改

//模拟实现strcpy函数：
#include <assert.h>
//void my_strcpy(char* dest, const char* src)
//{
//    assert(src != NULL);
//    assert(dest != NULL); //更加的严谨，防止传过来的指针为空指针而导致出现问题
//    while (*src != '\0')
//    {
//        *dest = *src;
//        dest++;
//        src++;
//    }
//    *dest = *src;
//}

//改进优化： (由于为了模拟strcpy函数的形式，返回值是char*类型，并且上面代码中while循环比较重复，可以进行优化)
//char* my_strcpy(char* dest, const char* src)
//{
//    assert(src != NULL);
//    assert(dest != NULL); //更加的严谨，防止传过来的指针为空指针而导致出现问题
//    char* ret = dest;
//    while (*dest++ = *src++)//while括号里面的内容很巧妙，首先++是优先于*的，而dest++是先取值再加1
//    {                       //所以等同于先*dest = *src，再两者各自加1，最后while再判断*dest
//        ;                   //而*dest得到的是一个字符，字符判断是转换成对应的ascii值，'\0'的ascii值为0
//    }                       //而且这个逻辑是先拷贝再判断，所以'\0'是拷贝完之后再跳出循环，优化了前面代码不能同时拷贝'\0'
//    return ret;//这个地方是不能直接返回dest，原因就是前面dest++了，已经不是首字符的地址
//}
//
//int main()
//{
//    char arr1[] = "Hello world";
//    char arr2[20] = "xxxxxxxxxxxxxxxx";//由于把源字符串的'\0'也拷贝到目标空间了，而%s打印时遇到\0就会停止只打印前面的内容
//    my_strcpy(arr2, arr1);             //所以arr2原本的内容再长经过strcpy函数后打印的结果也只是源字符串的内容
//    printf("%s\n", arr2);
//    return 0;
//}

//strcat 的使用和模拟实现
    //str - string cat - concatenate --> 字符串的连接
    //char * strcat ( char * destination, const char * source );
    //                       目的地(被连接对象)        源头(连接对象)
//int main()
//{
//    char arr1[20] = "Hello ";
//    char arr2[] = "world";
//    strcat(arr1, arr2);
//    printf("%s\n", arr1);
//    return 0;
//}
    //源字符串必须以 '\0' 结束。 //否则打印目标字符串和源字符串后会出现一段乱码
    //目标字符串中也得有 \0 ，否则没办法知道追加从哪里开始。 //导致打印完目标字符串后会有一段乱码再打印源字符串
    //目标空间必须有足够的大，能容纳下源字符串的内容。
    //目标空间必须可修改。

//模拟实现strcat函数：
//void my_strcat(char* dest, const char* src)
//{
//    while (*dest != '\0')
//    {
//        dest++;
//    }
//    while (*src != '\0')
//    {
//        *dest = *src;
//        dest++;
//        src++;
//    }
//    *dest = *src;
//}

//改进优化：
//char* my_strcat(char* dest, const char* src)
//{
//    assert(dest != NULL);
//    assert(src != NULL);
//    char* ret = dest; //和前面模拟实现strcpy函数一样，这里ret指针变量作用就是提前拷贝一份目标字符串的首字符地址
//    while (*dest != '\0')
//    {
//        dest++;
//    }
//    while (*dest++ = *src++)
//    {
//        ; //空语句
//    }
//    return ret;
//}
//
//int main()
//{
//    char arr1[20] = "Hello ";
//    char arr2[] = "world";
//    my_strcat(arr1, arr2);
//    printf("%s\n", arr1);
//
//    return 0;
//}

    //模拟实现strcat函数，字符串自己给自己追加，如何？
//char* my_strcat(char* dest, const char* src)
//{
//    assert(dest != NULL);
//    assert(src != NULL);
//    char* ret = dest;
//    while (*dest != '\0')
//    {
//        dest++;
//    }
//    while (*dest++ = *src++) //在这个地方程序就会崩溃，原因是dest和src两个形参都是arr1即首元素的地址
//    {                        //当dest跳到'\0'位置时，src解引用得到的是arr1第一个字符'a'赋给*dest时就把'\0'给覆盖成了'a'
//                             //此时当src++到用来arr1的'\0'的地址时，此时由于'\0'已经被'a'覆盖了，所以不会停下来
//        ; //空语句           //故会导致死循环，而地址一直往后跳则会到一定程度越界访问，程序崩溃
//    }
//    return ret;
//}
//
//int main()
//{
//    char arr1[20] = "abcdef";
//    my_strcat(arr1, arr1);
//    printf("%s\n", arr1);
//
//    return 0;
//}
    //虽然模拟实现的strcat函数，字符串自己给自己追加是不可行的，但c语言提供的strcat函数是可以实现的

//strcmp 的使用和模拟实现
    //str - string cmp - compare --> 比较两个字符串的内容
    //int strcmp ( const char * str1, const char * str2 );
    //标准规定：
    //第一个字符串大于第二个字符串，则返回大于0的数字
    //第一个字符串等于第二个字符串，则返回0
    //第一个字符串小于第二个字符串，则返回小于0的数字
    //那么如何判断两个字符串？ 比较两个字符串中对应位置上字符ASCII码值的大小，
    //如果两个字符串对应位置字符ASCII码值大小相等，则继续比较后面的位置，只要某个位置大小不一样，则后面的不再看
//int main()
//{
//    char arr1[] = "abcdef";
//    char arr2[] = "abccfe";
//    int ret = strcmp(arr1, arr2);
//    printf("%d\n", ret); //1
//    return 0;
//}

//模拟实现strcmp函数：
//int my_strcmp(const char* arr1, const char* arr2)
//{
//    assert(arr1 != NULL);
//    assert(arr2 != NULL);
//    while (*arr1 == *arr2)
//    {
//        if (*arr1 == '\0')
//        {
//            return 0;
//        }
//        arr1++;
//        arr2++;
//    }
//    if (*arr1 > *arr2)
//    {
//        return 1;
//    }
//    else
//        return -1;
//}
//
//int main()
//{
//    char arr1[] = "abcdef";
//    char arr2[] = "abccfe";
//    int ret = my_strcmp(arr1, arr2);
//    printf("%d\n", ret); //1
//    return 0;
//}

//长度受限制的字符串函数：strncpy、strncat、strncmp

//strncpy 函数的使用
    //char * strncpy ( char * destination, const char * source, size_t num );
    //                          目的地                   源头   需要拷贝的字符数量
    //拷贝num个字符从源字符串到目标空间，且如果num小于源字符串则不会拷贝'\0'，目标空间后面的元素仍会保留
    //如果源字符串的长度小于num，则拷贝完源字符串之后，在目标的后边追加0('\0')，直到num个。
//int main()
//{
//    char arr1[] = "abcdef";
//    char arr2[20] = "xxxxxxxxxx";
//    strncpy(arr2, arr1, 3);
//    printf("%s\n", arr2); //abcxxxxxxx
//    return 0;
//}

//strncat 函数的使用
    //char * strncat ( char * destination, const char * source, size_t num );
    //将source指向字符串的前num个字符追加到destination指向的字符串末尾，再追加一个'\0'字符
//int main()
//{
//    char arr1[] = "abcdef";
//    char arr2[20] = "xxx\0xxxxxxx";
//    strncat(arr2, arr1, 3);
//    printf("%s\n", arr2); //xxxabc,原因就是前num个字符追加完后还会再追加一个'\0'字符，使得后面xxxxx打印不出来
//    return 0;
//}
    //如果source 指向的字符串的长度小于num的时候，只会将字符串中到'\0'的内容追加到destination指向的字符串末尾
    //而不会像strncpy函数一样在目标的后边追加0('\0')，直到num个。

//strncmp函数的使用
    //int strncmp ( const char * str1, const char * str2, size_t num );
    //比较str1和str2的前num个字符，如果相等就继续往后比较，最多比较num个字母，
    //如果提前发现不一样，就提前结束，大的字符所在的字符串大于另外一个。如果num个字符都相等，就是相等返回0
//int main()
//{
//    char arr1[] = "abcdef";
//    char arr2[] = "abcefg";
//    int ret = strncmp(arr2, arr2, 3);
//    printf("%d\n", ret); //0
//    return 0;
//}

//strstr 的使用和模拟实现
    //char * strstr ( const char * str1, const char * str2);
//int main()
//{
//    char arr1[] = "This is an apple";
//    char* p = "is a";
//    char* p1 = "this";
//    char* p2 = "";
//    char* ret = strstr(arr1, p);
//    char* ret1 = strstr(arr1, p1);
//    char* ret2 = strstr(arr1, p2);
//    printf("%s\n", ret); //is an apple
//    if (ret1 != NULL)
//    {
//        printf("%s\n", ret1);
//    }
//    else
//        printf("找不到\n");
//    printf("%s\n", ret2); //This is an apple
//    return 0;
//}
    //函数返回字符串str2在字符串str1中第一次出现的位置,并且返回字符串str1中找到的位置之后的内容
    //如果在字符串str1中找不到字符串str2中内容，则返回值为空指针
    //如果字符串str2中没有内容相当于就一个'\0',则strstr返回的内容为字符串str1整个内容

//模拟实现strstr函数：
//char* my_strstr(const char* str1, const char* str2)
//{
//    char* s1 = NULL; //首先创建s1、s2变量的作用就是用这两个变量进行遍历对比两个字符串，而不是直接用形参遍历，能省下很多麻烦
//    char* s2 = NULL; 
//    char* cur = str1; //变量cur的作用就是每次比较s1和s2时如果不一样则加1，让str1到下一个字符进行比较
//    if (*str2 == '\0')
//    {
//        return (char*)str1; //由于函数的返回值为char*，而形参的类型为const char*，两个类型不一样，所以需要强制类型转换
//    }
//    while (*cur)
//    {
//        s1 = cur; 
//        //在循环开始就让cur赋给s1目的是，如果在遍历时存在str2前一部分字符匹配一样但又不是完全一样的
//        //则就是不满足要求，cur要继续加1往后判断，再回到循环开头时则s1再次被赋值，此时的地址则是上次判断位置的下一个字符地址
//        s2 = str2;
//        //循环开始把str2赋给s2，目的和上面一样，每次判断str2都是从头开始
//        while(*s1 == *s2)
//        {
//            s1++;
//            s2++;
//            if (*s2 == '\0')
//            {
//                return (char*)cur;
//            }
//        }
//        cur++;
//    }
//    return NULL;
//}
//
//int main()
//{
//    char arr1[] = "abcdefabcdef";
//    char arr2[] = "cde";
//    //char arr1[] = "abbbcdef";
//    //char arr2[] = "bbc";
//    //char arr1[] = "abbbcdef";
//    //char arr2[] = "bbq";
//    //char arr1[] = "cde";
//    //char arr2[] = "abcdefabcdef";
//    char* ret = my_strstr(arr1, arr2);
//    if (ret == NULL)
//    {
//        printf("找不到");
//    }
//    else
//        printf("%s\n", ret);
//    return 0;
//}

//strtok 函数的使用
    //char * strtok ( char * str, const char * sep);
    //sep参数指向一个字符串，定义了用作分隔符的“字符集合” eg：str为"abc@def.ghi" 则sep为"@."
    //第一个参数指定一个字符串，它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。 标记指的就是特殊字符、
    //strtok函数找到str中的下一个标记，并将其用'\0'结尾，返回一个指向这个标记的指针。
        //意思就是比如上面举的例子中的@和. 当strtok函数找到@时会把@替换成'\0',并且会记录返回@这个字符的地址
        //（注：strtok函数会改变被操作的字符串，所以被strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。）
    //strtok函数的第一个参数不为 NULL ，函数将找到str中第⼀个标记，strtok函数将保存它在字符串中的位置。
    //strtok函数的第⼀个参数为 NULL ，函数将在同⼀个字符串中“被保存的位置”开始，查找下⼀个标记。
    //如果字符串中不存在更多的标记，则返回 NULL 指针。

//int main()
//{
//    char arr1[] = "123456@qq.com";
//    char arr2[30] = { 0 }; //作为临时拷贝的变量
//    const char* sep = "@.";
//    char* ret = NULL;
//    strcpy(arr2, arr1); //char arr2[30] = "123456@qq.com"
//
//    //ret = strtok(arr2, sep);
//    //printf("%s\n", ret);
//    //ret = strtok(NULL, sep); //strtok函数的第⼀个参数为 NULL,所以开始的位置是
//    //printf("%s\n", ret);     //上一个strtok函数中的特殊符号地址开始查找下⼀个标记，
//    //ret = strtok(NULL, sep); //以此类推，有多少个特殊符号就调用多少次strtok(NULL, sep);
//    //printf("%s\n", ret);
//
//    //但是会出现一个问题，就是我们作为写代码的人是不清楚用户的字符串里面有多少个特殊符号，所以手动多次调用函数肯定不现实
//    //所以要利用循环来解决，而for循环可以解决问题，但非常巧妙
//
//    for (ret = strtok(arr2, sep);  ret != NULL;  ret = strtok(NULL, sep))
//    //首先要知道for循环括号内三个内容的意思，第一个是初始化，第二个是判断，第三个是调整
//    //先是运行第一个内容ret = strtok(arr2, sep)，然后判断第二个内容ret是否为NULL，如果不是则进入函数体打印
//    //然后再运行第三个内容调整，再判断ret是否为NULL，如果不是则进入函数体
//    //后面运行顺序就是第三个内容调整，再第二个内容判断，如果进入函数体，以此循环
//    //所以for循环第一个内容其实只运行了一次，和前面的代码完全一样，并且for循环不用管字符串多长都能打印
//    {
//        printf("%s\n", ret);
//    }
//    return 0;
//}